var Map = require("js/map").Map;
var Match = require("js/match").Match;
var Enemy = require("js/enemy").Enemy;
var Obstacle = require("js/obstacle").Obstacle;
var Radar = require("js/radar").Radar;
var Turret = require("js/turret").Turret;
var GameEntity = require("js/gameEntity").GameEntity;
var gamejs = require("gamejs");
var utils = require("js/utils");

var testMatch;
var testMap;
var width = 600;
var height = 400;

qModule("js/map", {
	setup: function() {
		testMatch = new Match();
		testMap = new Map(testMatch, width, height);
		testMatch.setMap(testMap);
		testMatch.start();
	}
});


test("Map (match, width, height) [constructor]", function () {
	// Normal map
	ok(testMap instanceof Map,
		"of for Map correctly created with argument: (Match, 600, 400)"
	);
	
	// Test for empty map
	var filled = false;
	for(var x = 0; (x < width) && ( ! filled); x++){
		for(var y = 0; (y < height) && ( ! filled); y++){
			filled = testMap.isFilled([x, y]);
		}
	}
	ok( ! filled, "ok for new empty map");
	
	// Test with invalid dimensions
	throws(
		function () {
			testMap2 = new Map(testMatch, 0, 0);
		},
		RangeError,
		"ok for invalid dimensions [0, 0][raise a RangeError]"
	);
	
	throws(
		function () {
			testMap2 = new Map(testMatch, -10, -10);
		},
		RangeError,
		"ok for invalid dimensions [-10, -10][raise a RangeError]"
	);
	
	// Test with invalid match
	throws(
		function () {
			testMap2 = new Map(undefined, 40, 40);
		},
		TypeError,
		"ok for match = undefined[raise a TypeError]"
	);
	
	throws(
		function () {
			testMap2 = new Map(null, 40, 40);
		},
		TypeError,
		"ok for match = null[raise a TypeError]"
	);
});

test("Map.getMatch()", function() {
	strictEqual(testMap.getMatch(), testMatch,
		"ok for the correct Match object returned"
	);
});

test("Map.getSize()", function() {
	var size = [width, height];
	deepEqual(testMap.getSize(), size, "ok for the correct dimensions returned");
});

test("Map.getEnemies()", function() {
	strictEqual(testMap.getEnemies(), testMap.enemies,
		"ok for the correct object returned"
	);
});

test("Map.getObstacles()", function() {
	strictEqual(testMap.getObstacles(), testMap.obstacles,
		"ok for the correct object returned"
	);
});

test("Map.getTurrets()", function() {
	strictEqual(testMap.getTurrets(), testMap.turrets,
		"ok for the correct object returned"
	);
});

test("Map.getRadars()", function() {
	strictEqual(testMap.getRadars(), testMap.radars,
		"ok for the correct object returned"
	);
});

test("Map.getProjectiles()", function() {
	strictEqual(testMap.getProjectiles(), testMap.projectiles,
		"ok for the correct object returned"
	);
});

test("Map.hasRadars()", function() {
	testMatch.changeCredit(5000);
	testMap = new Map(testMatch, 300, 260);
	testMatch.setMap(testMap);
	var testRadar = new Radar(testMatch, [55, 60]);
	testMap.addGameEntity(testRadar);
	ok(testMap.hasRadars(), "correctly return true with a radar");
	testMap.removeGameEntity(testRadar);
	ok(!testMap.hasRadars(), "correctly return false without a radar");
});

test("Map.hasTurrets()", function() {
	testMatch.changeCredit(5000);
	testMap = new Map(testMatch, 300, 260);
	testMatch.setMap(testMap);
	var testTurret = new Turret(testMatch, [55, 60]);
	testMap.addGameEntity(testTurret);
	ok(testMap.hasTurrets(), "correctly return true with a turret");
	testMap.removeGameEntity(testTurret);
	ok(!testMap.hasTurrets(), "correctly return false without a turrey");
});

test("Map.isInside(coordinates)", function() {
	// A point inside the map
	ok(testMap.isInside([10, 10]),
		"Test with argument: (point inside the Map)"
	);
	
	// Test on a point outside the map
	ok( ! testMap.isInside([width + 10, height + 10]),
		"Test with argument: (point outside the Map)"
	);
	
	// Test on top left angle
	ok(testMap.isInside([0, 0]), "Test with argument: ([0, 0])");
	
	// Test on top right angle
	ok(testMap.isInside([width - 1, 0]),
		"Test with argument: ([width - 1, 0])"
	);
	
	// Test on bottom right angle
	ok(testMap.isInside([width - 1, height - 1]),
		"Test with argument: ([width - 1, height - 1])"
	);
	
	// Test on bottom left angle
	ok(testMap.isInside([0, height - 1]),
		"Test with argument: ([0, height - 1])"
	);
	
	// Tests on some point just outside the top left angle
	ok( ! testMap.isInside([-1, 0]), "Test with argument: ([-1, 0])");
	ok( ! testMap.isInside([-1, -1]), "Test with argument: ([-1, -1])");
	ok( ! testMap.isInside([0, -1]), "Test with argument: ([0, -1])");
	
	// Tests on some point just outside the top right angle
	ok( ! testMap.isInside([width - 1, -1]),
		"Test with argument: ([width - 1, -1])"
	);
	ok( ! testMap.isInside([width, -1]),
		"Test with argument: ([width, -1])"
	);
	ok( ! testMap.isInside([width, 0]),
		"Test with argument: ([width, 0])"
	);
	
	// Tests on some point just outside the bottom right angle
	ok( ! testMap.isInside([width, height - 1]),
		"Test with argument: ([width, height - 1])"
	);
	ok( ! testMap.isInside([width, height]),
		"Test with argument: ([width, height])"
	);
	ok( ! testMap.isInside([width - 1, height]),
		"Test with argument: ([width - 1, height])"
	);
	
	// Tests on some point just outside the bottom left angle
	ok( ! testMap.isInside([0, height]),
		"Test with argument: ([0, height])"
	);
	ok( ! testMap.isInside([-1, height]),
		"Test with argument: ([-1, height])"
	);
	ok( ! testMap.isInside([-1, height - 1]),
		"Test with argument: ([-1, height - 1])"
	);
	
	// Tests that throws the right exception when using invalid coordinates
	throws(
		function() { testMap.isInside([1, ""]); },
		TypeError,
		"Test with argument: ([1, ''])[raise a TypeError]"
	);
	throws(
		function() { testMap.isInside([, 1]); },
		TypeError,
		"Test with argument: ([undefined, 1])[raise a TypeError]"
	);
	throws(
		function() { testMap.isInside([NaN, NaN]); }, // NaN is a "number" type
		TypeError,
		"Test with argument: ([NaN, NaN])[raise a TypeError]"
	);
});

test("Map.fill(coordinates)", function() {
	// invalid format coordinates
	throws(
		function() { testMap.fill([null, undefined]); },
		TypeError,
		"Test with argument: ([null, undefined])"
	);
	
	// out of range coordinates
	throws(
		function() { testMap.fill([-10, -5]); },
		RangeError,
		"Test with argument: ([-10, -5])"
	);
	
	throws(
		function() { testMap.fill([width, height]); },
		RangeError,
		"Test with argument: ([width, height])"
	);
	
	var cords = [4, 4];
	// Test on empty coordinates (a new Map is totally empty)
	testMap.fill(cords);
	ok(testMap.isFilled(cords),
		"Test with argument: (empty cordinates)"
	);
	
	// Test on already filled coordinates
	testMap.fill(cords);
	ok(testMap.isFilled(cords),
		"Test with argument: (filled cordinates)"
	);
});

test("Map.clear(coordinates)", function() {
	// invalid format coordinates
	throws(
		function() { testMap.clear([null, undefined]); },
		TypeError,
		"Test with argument: ([null, undefined])"
	);
	
	// out of range coordinates
	throws(
		function() { testMap.clear([-10, -5]); },
		RangeError,
		"Test with argument: ([-10, -5])"
	);
	
	throws(
		function() { testMap.clear([width, height]); },
		RangeError,
		"Test with argument: ([width, height])"
	);
	
	var cords = [4,4];
	// Test on empty cordinates (a new Map is totally empty)
	ok( ! testMap.isFilled(cords),
		"Test with argument: (empty cordinates)"
	);
	
	// Test on already cleared coordinates
	testMap.clear(cords);
	ok( ! testMap.isFilled(cords),
		"Test with argument: (cleared cordinates)"
	);
	
	// Test on filled coordinates
	testMap.fill(cords);
	testMap.clear(cords);
	ok( ! testMap.isFilled(cords),
		"Test with argument: (filled then cleared cordinates)"
	);
});

test("Map.isFilled(coordinates)", function() {
	// invalid format coordinates
	throws(
		function() { testMap.isFilled([null, undefined]); },
		TypeError,
		"Test with argument: ([null, undefined])"
	);
	
	//out of range coordinates
	throws(
		function() { testMap.isFilled([-10, -5]); },
		RangeError,
		"Test with argument: ([-10, -5])"
	);
	throws(
		function() { testMap.isFilled([width, height]); },
		RangeError,
		"Test with argument: ([100, 100])"
	);
	
	var cords = [4,4];
	// Test on empty cordinates (a new Map is totally empty)
	ok( ! testMap.isFilled(cords),
		"Test with argument: (empty cordinates)"
	);
	
	// Test on cleared coordinates
	testMap.clear(cords);
	ok( ! testMap.isFilled(cords),
		"Test with argument: (cleared cordinates)"
	);
	
	// Test on filled coordinates
	testMap.fill(cords);
	ok(testMap.isFilled(cords),
		"Test with argument: (filled cordinates)"
	);
});

test("Map.fillCircleWith(center, radius, navigable)", function () {
	// test with a circle in the middle of the map that doesn't go out
	// from the map border
	var center = [width / 2, height / 2];
	var radius = width / 5;
	var navigable = false;
	testMap.fillCircleWith(center, radius, navigable);
	
	var isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			var dis = utils.distance(center, [x, y]);
			if (dis <= radius) {
				isOk = testMap.isFilled([x, y]);
			}
			else {
				isOk = ! testMap.isFilled([x, y]);
			}
			// leaves a little tolerance
			if(( ! isOk) &&
				Math.abs(radius - dis) <= 0.001) {
				isOk = true;
			}
		}
	}
	ok(isOk, "Test with a not navigable circle in the middle of the map");
	
	// test with a circle covering all the map
	center = [width / 2, height / 2];
	radius = width + height;
	navigable = false;
	testMap.fillCircleWith(center, radius, navigable);
	
	isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			isOk = testMap.isFilled([x, y]);
		}
	}
	ok(isOk, "Test with a not navigable circle covering all the map");
	
	// test with a circle that goes out from the map border
	center = [width / 5, height / 2];
	radius = width / 3;
	navigable = true;
	testMap.fillCircleWith(center, radius, navigable);
	
	isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			var dis = utils.distance(center, [x, y]);
			if (dis <= radius) {
				isOk = ! testMap.isFilled([x, y]);
			}
			else {
				isOk = testMap.isFilled([x, y]);
			}
			// leaves a little tolerance
			if(( ! isOk) &&
				Math.abs(radius - dis) <= 0.001) {
				isOk = true;
			}
		}
	}
	ok(isOk, "Test with a navigable circle that goes out from the map border");
	
	// test for exception
	var invalidCenter = [-2, 5];
	var notCoordinates = [null, 1];
	center = [10, 10];
	
	// out of map coordinates
	throws(
		function() {
			testMap.fillCircleWith(invalidCenter, 2, true);
		},
		RangeError,
		"Test with arguments : ([-2, 5], 2, true) [raise a RangeError]"
	);
	
	// not coordinates
	throws(
		function() {
			testMap.fillCircleWith(notCoordinates, 2, true);
		},
		TypeError,
		"Test with arguments : ([null, 1], 2, true) [raise a TypeError]"
	);
	
	// radius lesser than zero
	throws(
		function() {
			testMap.fillCircleWith(center, -2, true);
		},
		RangeError,
		"Test with arguments : ([10, 10], -2, true) [raise a RangeError]"
	);
});

test("Map.fillCircle(center, radius)", function () {
	// this method only call fillCircleWith(center, radius, navigable)
	// that is already tested,
	// check only if the correct navigable value is given to the fillCircleWith
	var center = [width / 2, height / 2];
	var radius = 1;
	testMap.fillCircle(center, radius);
	
	ok(testMap.isFilled(center), "Test in map center");
});

test("Map.clearCircle(center, radius)", function () {
	// this method only call fillCircleWith(center, radius, navigable)
	// that is already tested,
	// check only if the correct navigable value is given to the fillCircleWith
	var center = [width / 2, height / 2];
	var radius = 1;
	// fill the center
	testMap.fill(center);
	
	testMap.clearCircle(center, radius);// to test
	
	ok( ! testMap.isFilled(center), "Test in map center");
});

test("Map.fillRectWith(rect, navigable)", function () {
	// test with a rect in the middle of the map that doesn't go out
	// from the map border
	var center = [Math.round(width / 2), Math.round(height / 2)];
	var dims = [Math.round(width / 5), Math.round(height / 5)];
	var topleft = utils.calcTopleft(center, dims);
	var testRect = new gamejs.Rect(topleft, dims);
	
	var navigable = false;
	testMap.fillRectWith(testRect, navigable);
	
	var isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			if (testRect.collidePoint([x, y])) {
				isOk = testMap.isFilled([x, y]);
			}
			else {
				isOk = ! testMap.isFilled([x, y]);
			}
		}
	}
	ok(isOk, "Test with a not navigable rect in the middle of the map");
	
	// test with a rect covering all the map
	center =  [Math.round(width / 2), Math.round(height / 2)];
	dims = [width, height];
	topleft = utils.calcTopleft(center, dims);
	testRect = new gamejs.Rect(topleft, dims);
	navigable = false;
	testMap.fillRectWith(testRect, navigable);
	
	isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			isOk = testMap.isFilled([x, y]);
		}
	}
	ok(isOk, "Test with a not navigable rect covering all the map");
	
	// test with a rect that goes out from the map border
	center =  [Math.round(width / 5), Math.round(height / 2)];
	dims =  [Math.round(width / 2), Math.round(height / 5)];
	topleft = utils.calcTopleft(center, dims);
	testRect = new gamejs.Rect(topleft, dims);
	navigable = true;
	testMap.fillRectWith(testRect, navigable);
	
	var isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			if (testRect.collidePoint([x, y])) {
				isOk = ! testMap.isFilled([x, y]);
			}
			else {
				isOk = testMap.isFilled([x, y]);
			}
		}
	}
	ok(isOk, "Test with a navigable rect that goes out from the map border");
});

test("Map.fillRect(rect)", function () {
	// this method only call fillRectWith(rect, navigable)
	// that is already tested,
	// check only if the correct navigable value is given to the fillRectWith
	var center =  [Math.round(width / 2), Math.round(height / 2)];
	var dims = [1, 1];
	var topleft = utils.calcTopleft(center, dims);
	var testRect = new gamejs.Rect(topleft, dims);
	testMap.fillRect(testRect);
	
	ok(testMap.isFilled(center), "Test in map center");
});

test("Map.clearRect(rect)", function () {
	// this method only call fillRectWith(rect, navigable)
	// that is already tested,
	// check only if the correct navigable value is given to the fillRectWith
	var center =  [Math.round(width / 2), Math.round(height / 2)];
	var dims = [1, 1];
	var topleft = utils.calcTopleft(center, dims);
	var testRect = new gamejs.Rect(topleft, dims);
	// fill the center
	testMap.fill(center);
	
	testMap.clearRect(testRect);// to test
	
	ok( ! testMap.isFilled(center), "Test in map center");
});

test("Map.addGameEntity(gameEntity)", function () {
	var center = [50, 50];
	
	// test with undefined
	throws(function() {
		testMap.addGameEntity(undefined);
	}, TypeError, "Test with argument = undefined [raise a TypeError]");
	
	// test with a GameEntity object not manageable by the map
	throws(
		function() {
			var upgList = [{image : IMAGE_ROOT + "radar.png"}];
			var ge = new GameEntity(testMatch, center, upgList);
			testMap.addGameEntity(ge);
		},
		TypeError,
		"Test with argument = GameEntity not manageable [raise a TypeError]"
	);
	
	// test with an entity that doesn't fill the map
	var enemy = new Enemy(testMatch, center, 0);
	var oldLength = testMap.getEnemies().sprites().length;
	testMap.addGameEntity(enemy);
	var newLength = testMap.getEnemies().sprites().length;
	strictEqual(newLength, oldLength + 1, "Test with argument = Enemy");
	
	// Test for the empty map
	var filled = false;
	for(var x = 0; (x < width) && ( ! filled); x++){
		for(var y = 0; (y < height) && ( ! filled); y++){
			filled = testMap.isFilled([x, y]);
		}
	}
	ok( ! filled, "ok for Map is still empty");
	
	// test with an entity that fills the map
	var testObs = new Obstacle(testMatch, center, 0);
	var oldLength = testMap.getObstacles().sprites().length;
	testMap.addGameEntity(testObs);
	var newLength = testMap.getObstacles().sprites().length;
	strictEqual(newLength, oldLength + 1, "Test with argument = Obstacle");
	
	var obstructiveRadius = testObs.getObstructiveRadius();
	var isOk = true;
	for (var y = 0; (y < height) && (isOk); y++) {
		for (var x = 0; (x < width) && (isOk); x++) {
			var dis = utils.distance(center, [x, y]);
			if (dis <= obstructiveRadius) {
				isOk = testMap.isFilled([x, y]);
			}
			else {
				isOk = ! testMap.isFilled([x, y]);
			}
			//leaves a little tolerance
			if(( ! isOk) &&
				Math.abs(obstructiveRadius - dis) <= 0.001) {
				isOk = true;
			}
		}
	}
	ok(isOk, "ok the filling for an Obstacle");
	
	// test with filling entity in an occupied area
	var radar = new Radar(testMatch, center);
	throws(function () {
		testMap.addGameEntity(radar);
	},
	/InvalidPositionError/,
	"Test with argument = Radar in occupied area [raise a InvalidPositionError]");
	
	testMap.removeGameEntity(testObs);
	
	// test without credit
	testMatch.changeCredit(-testMatch.credit);
	throws(function () {
		testMap.addGameEntity(new Radar(testMatch, center));
	},
	/InvalidOperationError/,
	"Test with argument = Radar without credit [raise a InvalidOperationError]");
});

test("Map.removeGameEntity(gameEntity)", function() {
	var center = [50, 50];
	
	// test with undefined
	throws(function() {
		testMap.removeGameEntity(undefined);
	}, TypeError, "Test with argument = undefined [raise a TypeError]");
	
	// test with a GameEntity object not manageable by the map
	throws(
		function() {
			var upgList = [{image : IMAGE_ROOT + "radar.png"}];
			var ge = new GameEntity(testMatch, center, upgList);
			testMap.removeGameEntity(ge);
		},
		TypeError,
		"Test with argument = GameEntity not manageable [raise a TypeError]"
	);
	
	// test with an entity object not added to the map
	throws(
		function() {
			var radar = new Radar(testMatch, center);
			testMap.removeGameEntity(radar);
		},
		/InvalidOperationError/,
		"Test with argument = Radar not added [raise a /InvalidOperationError/]"
	);
	
	// test with an entity that doesn't fill the map
	var enemy = new Enemy(testMatch, center, 0);
	testMap.addGameEntity(enemy);
	var oldLength = testMap.getEnemies().sprites().length;
	testMap.removeGameEntity(enemy);
	var newLength = testMap.getEnemies().sprites().length;
	strictEqual(newLength, oldLength - 1, "Test with argument = Enemy");
	
	// test with an entity that fills the map
	var testObs = new Obstacle(testMatch, center, 0);
	testMap.addGameEntity(testObs);
	var oldLength = testMap.getObstacles().sprites().length;
	testMap.removeGameEntity(testObs);
	var newLength = testMap.getObstacles().sprites().length;
	strictEqual(newLength, oldLength - 1, "Test with argument = Obstacle");
	
	// Test for the empty map
	var filled = false;
	for(var x = 0; (x < width) && ( ! filled); x++){
		for(var y = 0; (y < height) && ( ! filled); y++){
			filled = testMap.isFilled([x, y]);
		}
	}
	ok( ! filled, "ok for Map empty after the remove");
});

test("Map.adjacent(coordinates)", function() {
	testMap = new Map(testMatch, 100, 80);
	testMatch.setMap(testMap);
	
	throws(function() {
		testMap.adjacent([-1, 0]);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.adjacent([undefined, 10]);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
	
	var isOk = true;
	var startPoint = [10, 5];
	var adjacent =  testMap.adjacent(startPoint);
	for(i = 0; (i < adjacent.length) && (isOk); i++) {
		isOk = testMap.isInside(adjacent[i]) && ! testMap.isFilled(adjacent[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (adjacent[i])[X]);
			var dy = Math.abs(startPoint[Y] - (adjacent[i])[Y]);
			isOk = ! ((dx > 1) || (dy > 1));
		}
	}
	ok(isOk, "ok for valid coordinates [10, 5]");
	
	var isOk = true;
	var startPoint = [10, 0];
	var adjacent =  testMap.adjacent(startPoint);
	for(i = 0; (i < adjacent.length) && (isOk); i++) {
		isOk = testMap.isInside(adjacent[i]) && ! testMap.isFilled(adjacent[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (adjacent[i])[X]);
			var dy = Math.abs(startPoint[Y] - (adjacent[i])[Y]);
			isOk = ! ((dx > 1) || (dy > 1));
		}
	}
	ok(isOk, "ok for valid marginal coordinates [10, 0]");
});

test("Map.reachable(coordinates, stepLength)", function() {
	testMap = new Map(testMatch, 100, 80);
	testMatch.setMap(testMap);
	var stepLength = 5;
	
	throws(function() {
		testMap.reachable([-1, 0], stepLength);
	}, RangeError, "ok for invalid coordinates [-1, 0][raise a RangeError]");
	
	throws(function() {
		testMap.reachable([undefined, 10], stepLength);
	}, TypeError, "ok for invalid coordinates [undefined, 10][raise a TypeError]");
	
	var isOk = true;
	var startPoint = [10, 15];
	var reachable =  testMap.reachable(startPoint, stepLength);
	for(i = 0; (i < reachable.length) && (isOk); i++) {
		isOk = testMap.isInside(reachable[i]) && ! testMap.isFilled(reachable[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (reachable[i])[X]);
			var dy = Math.abs(startPoint[Y] - (reachable[i])[Y]);
			isOk = ! ((dx > stepLength) || (dy > stepLength));
		}
	}
	ok(isOk, "ok for valid coordinates [10, 15]");
	
	var isOk = true;
	var startPoint = [10, 0];
	var reachable =  testMap.reachable(startPoint, stepLength);
	for(i = 0; (i < reachable.length) && (isOk); i++) {
		isOk = testMap.isInside(reachable[i]) && ! testMap.isFilled(reachable[i]);
		if(isOk) {
			var dx = Math.abs(startPoint[X] - (reachable[i])[X]);
			var dy = Math.abs(startPoint[Y] - (reachable[i])[Y]);
			isOk = ! ((dx > stepLength) || (dy > stepLength));
		}
	}
	ok(isOk, "ok for valid marginal coordinates [10, 0]");
});

test("Map.actualDistance(startPoint, endPoint)", function() {
	testMap = new Map(testMatch, 100, 80);
	testMatch.setMap(testMap);
	
	throws(function() {
			testMap.actualDistance([-1, 0], [10, 0]);
		}, RangeError,
		"ok for invalid startPoint coordinates [-1, 0][raise a RangeError]"
	);
	
	throws(function() {
			testMap.actualDistance([10, 0], [-1, 0]);
		}, RangeError,
		"ok for invalid endPoint coordinates [-1, 0][raise a RangeError]"
	);
	
	throws(function() {
			testMap.actualDistance([undefined, 10], [-1, 0]);
		}, TypeError,
		"ok for invalid startPoint coordinates [undefined, 10][raise a TypeError]"
	);
	
	var isOk = true;
	var startPoint = [10, 20];
	var adjacent =  testMap.adjacent(startPoint);
	for(i = 0; (i < adjacent.length) && (isOk); i++) {
		var dx = startPoint[X] - (adjacent[i])[X];
		var dy = startPoint[Y] - (adjacent[i])[Y];
		var actDistance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
		isOk = (actDistance.toFixed(2) == testMap.actualDistance(startPoint, adjacent[i]));
	}
	ok(isOk, "ok for valid coordinates [10, 20]");
});

test("Map.estimatedDistance(startPoint, endPoint)", function() {
	testMap = new Map(testMatch, 100, 80);
	testMatch.setMap(testMap);
	
	throws(function() {
			testMap.estimatedDistance([-1, 0], [10, 0]);
		}, RangeError,
		"ok for invalid startPoint coordinates [-1, 0][raise a RangeError]"
	);
	
	throws(function() {
			testMap.estimatedDistance([10, 0], [-1, 0]);
		}, RangeError,
		"ok for invalid endPoint coordinates [-1, 0][raise a RangeError]"
	);
	
	throws(function() {
			testMap.estimatedDistance([undefined, 10], [-1, 0]);
		}, TypeError,
		"ok for invalid startPoint coordinates [undefined, 10][raise a TypeError]"
	);
	
	var isOk = true;
	var startPoint = [33, 40];
	var reachable =  testMap.reachable(startPoint, 21);
	for(i = 0; (i < reachable.length) && (isOk); i++) {
		var dx = Math.abs(startPoint[X] - (reachable[i])[X]);
		var dy = Math.abs(startPoint[Y] - (reachable[i])[Y]);
		isOk = (dx + dy === testMap.estimatedDistance(startPoint, reachable[i]));
	}
	ok(isOk, "ok for points distanced by 21 from valid coordinates [33, 40]");
});